﻿using System;
using System.Configuration;
using System.Text;
using UDS.Management.Data.Package;
using UDS.Common;
using System.Reflection;
using System.Data.SqlClient;
using UDS.Management.Data;
using UDS.Management.Data.Deployment;

namespace UDS.DBInstaller
{
    public class Installer
    {
        static void Main(string[] args)
        {
            // Read command arguments
            var cmdArgs = new InstallerArgs();
            try
            {
                ArgParser.Parse(cmdArgs, args);
                if (cmdArgs.ShowUsage)
                {
                    cmdArgs.Usage(null);
                    return;
                }
            }
            catch (Exception exp)
            {
                cmdArgs.Usage(exp.Message);
                return;
            }

            string assemblyLocation = cmdArgs.AssemblyLocation;
            string cs = cmdArgs.ConnectionString;

            // Read the connection string
            if (string.IsNullOrEmpty(cs))
            {
                cs = ConfigurationManager.AppSettings["ConnectionString"];
                if (string.IsNullOrEmpty(cs))
                {
                    Logger.WriteError("The configuration key 'ConnectionString' is required!");
                    goto END;
                }
            }
            if (string.IsNullOrEmpty(assemblyLocation))
            {
                assemblyLocation = ConfigurationManager.AppSettings["AssemblyLocation"];
                if (string.IsNullOrEmpty(assemblyLocation))
                {
                    Logger.WriteError("The configuration key 'AssemblyLocation' is required!");
                    goto END;
                }
            }

            // Checks whether the connection string is valid? 
            var csBuilder = new SqlConnectionStringBuilder(cs);
            if (string.IsNullOrEmpty(csBuilder.DataSource))
            {
                Logger.WriteError("The data source is missing in the connection string.");
                goto END;
            }

            // Verify the user want to perform the install
            Logger.WriteLine("#");
            Logger.WriteLine("# Be sure to BACKUP database {0} at {1} before the installation!", csBuilder.InitialCatalog, csBuilder.DataSource);
            Logger.Write("# Are you sure to continue? [Y/N] ");
            while (true)
            {
                ConsoleKeyInfo key = Console.ReadKey(true);
                switch (key.KeyChar)
                {
                    case 'y':
                    case 'Y':
                        Logger.WriteLine("Y");
                        goto DO_INSTALL;

                    case 'n':
                    case 'N':
                        // Cancelling the installation
                        Logger.WriteLine("N");
                        goto END;

                    default:
                        Console.Beep();
                        break;
                }
            }

        DO_INSTALL:
            // Initialize the logger here
            Logger.Init();

            try
            {
                PackageLoader packageLoader = new PackageLoader();
                Assembly assembly = Assembly.LoadFile(System.IO.Path.GetFullPath(assemblyLocation));
                packageLoader.LoadPackages(assembly);

                IDBPackage package = null;
                foreach (string packageName in packageLoader.GetPackageNames())
                {
                    // An assembly is assumed to contain only 1 DB package.
                    package = packageLoader.GetPackage(packageName);
                    break;
                }
                if (package == null)
                    throw new InvalidOperationException(string.Format("Failed to load database package from \"{0}\".", assemblyLocation));

                if (!string.IsNullOrEmpty(csBuilder.InitialCatalog))
                {
                    // Verify the database name is equal to the one specified in the manifest file
                    if (0 != string.Compare(package.GetDatabaseName(), csBuilder.InitialCatalog, true))
                        throw new InvalidOperationException(string.Format("The expected database name is \"{0}\", other than \"{1}\".", package.GetDatabaseName(), csBuilder.InitialCatalog));
                }
                else
                {
                    csBuilder.InitialCatalog = package.GetDatabaseName();
                }

                ServerLayoutHelper slHelper = new ServerLayoutHelper(csBuilder.ConnectionString);
                Layout layout = new Layout(slHelper);

                // TODO:
                // The name of the slave db should be configured. 
                // But for now, the hard-coded name is OK as long as we just have 1 slave DB.
                layout.SlaveName = "Slave DB 1"; 
                layout.LogEvent += delegate(object sender, UDS.Management.Data.Database.DatabaseUpdateEvent update)
                {
                    if (!string.IsNullOrEmpty(update.Name))
                    {
                        Logger.WriteLine("[{0}] {1}", update.Context, update.Name);
                    }
                };

                DatabaseData dbData = new DatabaseData(package, new Database(csBuilder));
                layout.Update(
                    new DatabaseData[] { dbData },
                    Layout.UpdateMode.All,
                    null,
                    null,
                    true);
            }
            catch (Exception exp)
            {
                Logger.WriteError("# Failed to update database.");
                Logger.LogException(exp);
            }

        END:
            Logger.Flush();
            Console.WriteLine("# ");
            Console.WriteLine("# Application closed.");
            Console.WriteLine("# ");
        }
    }
}
